// -*-mode:c++; coding:utf-8-*-

#ifndef _KB_BINARY_OARCHIVE_
#define _KB_BINARY_OARCHIVE_

#include <unistd.h>

#include <string>
#include <vector>
#include <cstring>

#ifndef NBOOST
#	include <boost/asio/buffer.hpp>
#endif	// NBOOST

#include "byteorder.hpp"
#include "detail/binary_archive_base.hpp"

namespace kb
{
#ifdef NDEBUG
	template<typename Byteorder,
		 typename Config = details::binary_archive_config_default>
#else
	template<typename Byteorder,
		 typename Config = details::binary_archive_config_debug> // debug feature enabled
#endif	// NDEBUG
	class binary_oarchive
		: public details::binary_archive_base<Config>
	{
	public:
		typedef details::binary_archive_base<Config> base_type;
		typedef binary_oarchive<Byteorder, Config> this_type;
		typedef Byteorder byteorder;

		using base_type::fail;
		using base_type::good;
		using base_type::clear_status;

	public:
		binary_oarchive()
			: m_data(NULL)
			, m_end(NULL)
			, m_pos(NULL) {}
		
		binary_oarchive(char *data, size_t size)
			: m_data(data)
			, m_end(data + size)
			, m_pos(data) {}

		binary_oarchive(const char *begin, const char *end)
			: m_data(begin)
			, m_end(end)
			, m_pos(begin) {}

#ifndef NBOOST
		explicit
		binary_oarchive(const boost::asio::mutable_buffer &buffer)
			: m_data(boost::asio::buffer_cast<char*>(buffer))
			, m_end(boost::asio::buffer_cast<char*>(buffer)
				+ boost::asio::buffer_size(buffer))
			, m_pos(boost::asio::buffer_cast<char*>(buffer)) {}
#endif	// NBOOST

		template<typename T>
		inline
		this_type &operator<<(const T &obj) {
			save(obj);
			return *this;
		}

		template<typename T>
		inline
		bool save(const T &obj) {
			return details::binary_oarchive_helper<this_type, T>::save(*this, obj);
		}

		template<typename T>
		inline
		bool save_object(const T &obj) {
			return obj.save(*this);
		}

		template<typename Integer>
		inline
		bool save_integer(Integer n) {
			if(fail())
			{
				return false;
			}
			char * const new_pos = m_pos + sizeof(Integer);
			if(base_type::check_data_bound(m_data, new_pos, m_end) == false)
			{
				return false;
			}
			Integer * const p = reinterpret_cast<Integer*>(m_pos);
			*p = byteorder_cast<host_byteorder, byteorder>(n);
			m_pos = new_pos;
			return true;
		}

		template<typename CharT, typename Traits, typename Alloc>
		inline
		bool save_string(const std::basic_string<CharT, Traits, Alloc> &str) {
			return basic_save_string<uint32_t>(str);
		}

		template<typename CharT, typename Traits, typename Alloc>
		inline
		bool save_string64(const std::basic_string<CharT, Traits, Alloc> &str) {
			return basic_save_string<uint64_t>(str);
		}

		template<typename SizeT, typename CharT, typename Traits, typename Alloc>
		inline
		bool basic_save_string(const std::basic_string<CharT, Traits, Alloc> &str) {
			typedef std::basic_string<CharT, Traits, Alloc> string_type;
			typedef typename string_type::value_type char_type;

			if(base_type::template check_size_limits<SizeT, std::size_t>(str.size()) == false)
			{
				return false;
			}
			SizeT size = static_cast<SizeT>(str.size());
			if(save_integer(size) == false)
			{
				return false;
			}
			const std::size_t data_bytes = size * sizeof(char_type);
			char * const new_pos = m_pos + data_bytes;
			if(base_type::check_data_bound(m_data, new_pos, m_end) == false)
			{
				return false;
			}
			std::memcpy(m_pos, static_cast<const char *>(str.data()), data_bytes);
			m_pos = new_pos;
			return true;
		}

		template<typename T, typename Alloc>
		inline
		bool save_vector(const std::vector<T, Alloc> &vec) {
			return basic_save_vector<uint32_t>(vec);
		}

		template<typename T, typename Alloc>
		inline
		bool save_vector64(const std::vector<T, Alloc> &vec) {
			return basic_save_vector<uint64_t>(vec);
		}

		template<typename SizeT, typename T, typename Alloc>
		inline
		bool basic_save_vector(const std::vector<T, Alloc> &vec) {
			typedef std::vector<T, Alloc> vector_type;
			typedef typename vector_type::value_type value_type;
			typedef typename vector_type::const_iterator const_iterator;

			if(base_type::template check_size_limits<SizeT, std::size_t>(vec.size()) == false)
			{
				return false;
			}
			SizeT size = static_cast<SizeT>(vec.size());
			if(save_integer(size) == false)
			{
				return false;
			}
			const const_iterator end = vec.end();
			for(const_iterator it = vec.begin();
			    it != end; ++it)
			{
				if(save(*it) == false)
				{
					return false;
				}
			}
			return true;
		}

		inline
		bool seek(ssize_t pos, int whence) {
			char * const base =
				((whence == SEEK_SET) ? m_data :
				 ((whence == SEEK_CUR) ? m_pos :
				  ((whence == SEEK_END) ? m_end : NULL)));
			if(base == NULL)
			{
				base_type::set_status(base_type::S_ERROR_BAD_SEEK_TYPE);
				return false;
			}
			char * const new_pos = base + pos;
			if(base_type::check_data_bound(m_data, new_pos, m_end) == false)
			{
				return false;
			}
			m_pos = new_pos;
			return true;
		}

		bool empty() const
			{ return m_pos == m_end; }

		char * left_buffer() const
			{ return m_pos; }

		std::size_t left_buffer_size() const
			{ return std::size_t(m_end - m_pos); }

		char * used_buffer() const
			{ return m_data; }

		std::size_t used_buffer_size() const
			{ return std::size_t(m_pos - m_data); }

	private:
		char * const m_data;
		char * const m_end;
		char * m_pos;
	};

} // namespace kb

#endif	// _KB_BINARY_OARCHIVE_
